﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Jsdl.Domain.Intermediate;
using Jsdl.Domain.Type;
using Newtonsoft.Json;

namespace Jsdl
{
    public class JsdlTypeGenerator
    {
        public JsdlTypeGenerator()
        {
            Types = new List<JsdlType>();
        }

        public List<JsdlType> Types { get; set; }

        public T GenerateType<T>(Type type) where T : JsdlType, new()
        {
            return (T)GenerateTypeTest<T>(type);
        }

        private object GenerateTypeTest<T>(Type type) where T : JsdlType, new()
        {
            // value type
            var nullable = type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);

            if (type == typeof(byte[]))
                return new T { TypeRaw = ConvertName("string") };

            if (type == typeof(DateTime?) || type == typeof(DateTime))
                return new JsdlProperty { TypeRaw = ConvertName("string"), Format = "date-time", RequiredRaw = !nullable };

            if (nullable)
                type = type.GenericTypeArguments[0];

            var name = type.Name;
            if (type.IsValueType || type == typeof(string))
            {
                if (type == typeof(Int32) || type == typeof(Int64))
                    name = "integer";
                if (type == typeof(Decimal) || type == typeof(Double) || type == typeof(float))
                    name = "number";

                var result = new T { TypeRaw = ConvertName(name) };
                if (type != typeof(string) && typeof(T) == typeof(JsdlProperty))
                    ((JsdlProperty)(object)result).RequiredRaw = !nullable;
                return result;
            }

            // object type
            if (Types.All(t => t.Name != name))
            {
                if (type.IsGenericType && (
                    type.GetGenericTypeDefinition() == typeof(IEnumerable<>) ||
                    type.GetGenericTypeDefinition() == typeof(List<>) ||
                    type.GetGenericTypeDefinition() == typeof(ICollection<>)))
                {
                    return new T
                    {
                        TypeRaw = "array",
                        Items = (JsdlType)GenerateTypeTest<JsdlType>(type.GetGenericArguments()[0])
                    };
                }

                if (type.BaseType == typeof(Array))
                {
                    return new T
                    {
                        TypeRaw = "array",
                        Items = (JsdlType)GenerateTypeTest<JsdlType>(type.GetElementType())
                    };
                }

                var newType = new JsdlType();
                newType.Name = name;
                Types.Add(newType); // add here to avoid recursions

                newType.TypeRaw = "object";
                newType.Properties = new Dictionary<string, JsdlProperty>();
                foreach (var prop in type.GetProperties())
                {
                    if (prop.GetCustomAttribute<JsonIgnoreAttribute>() == null)
                    {
                        var propertyName = prop.Name;

                        var jsonAttribute = prop.GetCustomAttribute<JsonPropertyAttribute>();
                        if (jsonAttribute != null && !string.IsNullOrEmpty(jsonAttribute.PropertyName))
                            propertyName = jsonAttribute.PropertyName;

                        newType.Properties.Add(propertyName, (JsdlProperty)GenerateTypeTest<JsdlProperty>(prop.PropertyType));
                    }
                }
            }

            return new T { TypeRaw = name };
        }

        public string ConvertName(string name, bool upper = false)
        {
            if (upper)
                return name.Substring(0, 1).ToUpper() + name.Substring(1);
            return name.Substring(0, 1).ToLower() + name.Substring(1);
        }
    }
}
